#include "tcpsocket.h"
#include <thread>
#include <set>
#include <map>
#include <mutex>
#ifndef __linux__
#   include <WinSock2.h>
#   include <ws2tcpip.h>
#   include <mstcpip.h> //struct keep_alive
#else
#   include <stdio.h>
#   include <sys/types.h>
#   include <sys/socket.h>
#   include <unistd.h>
#   include <arpa/inet.h>
#   define SOCKET int
#   define INVALID_SOCKET (-1)
#   define SOCKET_ERROR (-1)
#endif
#include <cstring>
#include <signal.h>


namespace tool {
namespace net {


class TcpServerPrivate
{
public:
    TcpServerPrivate(DataListener pListener, ConnListener pConnector)
        :m_socket(INVALID_SOCKET)
        , m_pListener(std::move(pListener))
        , m_pConnector(std::move(pConnector))
        , m_run(false)
    {
        std::memset(&m_serverAddr, 0, sizeof(m_serverAddr));
        INIT_WIN_SOCK;
    }

    ~TcpServerPrivate()
    {
        CLEAN_WIN_SOCK;
        printf("recv thread size:%zu\n", m_recvThd.size());
        destroy();
    }

    bool init(const std::string& serverAddr, uint16_t serverPort, int maxClientNum)
    {
        //1. Create Socket
        m_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (m_socket == INVALID_SOCKET)
        {
            DEBUGSOCK("socket");
            destroy();
            return false;
        }

        int reuse = 1;
        NETOPTSET(m_socket, SOL_SOCKET, SO_REUSEADDR, reuse);

#ifdef _WIN32
        int wait_time = 3000;
#else
        timeval wait_time{ 3,0 };
#endif
        NETOPTSET(m_socket, SOL_SOCKET, SO_RCVTIMEO, wait_time);

        //2. Bind
        std::memset(&m_serverAddr, 0, sizeof(m_serverAddr));
        m_serverAddr.sin_family = AF_INET;
        m_serverAddr.sin_port = htons(serverPort);
        m_serverAddr.sin_addr.s_addr = inet_addr(serverAddr.data());//htonl(INADDR_ANY);
        if (bind(m_socket, reinterpret_cast<sockaddr*>(&m_serverAddr), sizeof(m_serverAddr)) < 0) {
            DEBUGSOCK("bind");
            return false;
        }
        //3. Listen
        if (listen(m_socket, maxClientNum) < 0) {
            DEBUGSOCK("listen");
            destroy();
            return false;
        }

        //4. Receive
        launchThread();
        return true;
    }

    void destroy()
    {
        m_run = false;
        if (m_thread.joinable())
            m_thread.join();
        for (auto& pair : m_recvThd)
            pair.second.join();
        m_socket = INVALID_SOCKET;
    }

    bool isConnected() const
    {
        return m_socket != INVALID_SOCKET;
    }

    void notifyForQuit()
    {
        m_run = false;
    }
    void sendData(const char* data, size_t size)
    {
        for (auto& pair : m_recvThd)
        {
            auto ret = send(pair.first, data, size, 0);
            if (ret < 0) {
                DEBUGSOCK("send");
            }
            else if (ret == 0) {
                printf("Server send 0Byte\n");
            }
        }
    }

    const char* addr() const
    {
        return inet_ntoa(m_serverAddr.sin_addr);
    }

    uint16_t port() const
    {
        return ntohs(m_serverAddr.sin_port);
    }
private:
    void closeClient(SOCKET clientFd)
    {
#ifdef _WIN32
        closesocket(clientFd);
#else
        close(clientFd);
#endif
    }
    void launchThread()
    {
        //Create a loop thread to monitor new connect,
        //For each new connect,create a new thread to monitor data sended by client socket
        m_run = true;
        m_thread = std::thread([this]() {
            sockaddr_in clientAddr;
            socklen_t addrLen = sizeof(clientAddr);

            while (m_run) {
                SOCKET confd = accept(m_socket, reinterpret_cast<sockaddr*>(&clientAddr), &addrLen);
                if (confd == SOCKET_ERROR) {
#ifdef _WIN32
                    if (WSAGetLastError() != WSAETIMEDOUT)
#else
                    if (errno != EAGAIN)

#endif
                        DEBUGSOCK("accept");
                    continue;
                }
                //
                if (m_recvThd.count(confd) != 0)
                {
                    printf("Warning:A Repeat client sock\n");
                    continue;
                }

                notifyConnectState(confd, true);
                { //insert
                    std::lock_guard<std::mutex> lock(m_mutex);
                    m_recvThd[confd] = std::thread(std::bind(&TcpServerPrivate::recvThread, this, confd));
                }
            }
            });
    }

    void recvThread(SOCKET confd)
    {
        //printf("Create A receive Thread for cliend sock %d\n",confd);
        const int BUFF_SIZE = 2048;
        char buff[BUFF_SIZE] = "";

        int reuse = 1;
        NETOPTSET(confd, SOL_SOCKET, SO_REUSEADDR, reuse);
#ifdef _WIN32
        int wait_time = 3000;
#else
        timeval wait_time{ 3,0 };
#endif
        NETOPTSET(confd, SOL_SOCKET, SO_RCVTIMEO, wait_time);

#ifdef _WIN32
        tcp_keepalive alive{
            1,
            5,//保活探测消息的发送频率。默认值为75s
            5 //允许的持续空闲时间。默认值为7200s（2h）
        };
        NETOPTSET(confd, SOL_SOCKET, SO_KEEPALIVE, alive);
#endif

        while (m_run) {
            auto size = recv(confd, buff, BUFF_SIZE, 0);
            if (size < 0) {
#ifdef _WIN32
                if (WSAGetLastError() != WSAETIMEDOUT)
#else
                if (errno != EAGAIN)
#endif
                {
                    DEBUGSOCK("recv");
                    break;
                }
            }
            else if (size == 0) {//TODO...
               //it means socket had been closed
                break;
            }
            else {
                m_pListener(buff, size);
            }
        }
        {
            std::lock_guard<std::mutex> lock(m_mutex);
            //delete thread from here
            if (m_recvThd.count(confd) > 0) {
                notifyConnectState(confd, false);
                closeClient(confd);
                auto& thd = m_recvThd[confd];
                thd.detach();
                m_recvThd.erase(confd);
            }
        }
    }

    void notifyConnectState(SOCKET confd, bool state)
    {
        if (m_pConnector) {
            struct sockaddr sa;
            socklen_t len = sizeof(sa);
            if (!getpeername(confd, &sa, &len))
                m_pConnector(NetAddress(reinterpret_cast<sockaddr*>(&sa)), state);
            else
                m_pConnector({}, state);//TODO... will it happen?
        }
    }

private:
    SOCKET                  m_socket;
    DataListener            m_pListener;
    ConnListener            m_pConnector;
    std::thread             m_thread;
    bool                    m_run;
    sockaddr_in             m_serverAddr;
    std::mutex              m_mutex;
    std::map<SOCKET, std::thread>    m_recvThd;
};

TcpServer::TcpServer(DataListener pListener, ConnListener pConnector)
    :d(new TcpServerPrivate(pListener, pConnector))
{}

TcpServer::~TcpServer()
{
    delete d;
}

void TcpServer::notifyForQuit()
{
    d->notifyForQuit();
}

bool TcpServer::connect(const std::string& addr, uint16_t port, int maxClientNum)
{
    return d->init(addr, port, maxClientNum);
}

bool TcpServer::connect(const NetAddress& na, int maxClientNum)
{
    return d->init(TransAddr(na.ipValue()), na.port(), maxClientNum);
}

void TcpServer::disconnect()
{
    d->destroy();
}

bool TcpServer::isConnected() const
{
    return d->isConnected();
}

void TcpServer::sendData(const char* data, uint32_t size)
{
    d->sendData(data, size);
}



class TcpClientPrivate
{
public:
    TcpClientPrivate(DataListener pListener, const std::string& serverAddr, uint16_t serverPort)
        :m_socket(INVALID_SOCKET)
        , m_pListener(std::move(pListener))
        , m_run(false)
    {
        INIT_WIN_SOCK;

        std::memset(&m_serverAddr, 0, sizeof(m_serverAddr));
        m_serverAddr.sin_family = AF_INET;
        m_serverAddr.sin_port = htons(serverPort);
        m_serverAddr.sin_addr.s_addr = inet_addr(serverAddr.data());
#ifdef __linux__
        signal(SIGPIPE, SIG_IGN);
#endif
        m_run = true;
        m_connThread = std::thread(&TcpClientPrivate::autoConnect, this);
    }

    ~TcpClientPrivate()
    {
        CLEAN_WIN_SOCK;
        destroy();
    }

    void destroy()
    {
        notifyForQuit();
        if (m_recvThread.joinable())
            m_recvThread.join();
        if (m_connThread.joinable())
            m_connThread.join();
#ifdef _WIN32
        closesocket(m_socket);
#else
        close(m_socket);
#endif
        m_socket = INVALID_SOCKET;
    }

    bool isConnected() const
    {
        if (m_socket == INVALID_SOCKET)
            return false;
#ifdef __linux
        if ((send(m_socket, nullptr, 0, 0) < 0) && (errno == EPIPE))
#else
        if (send(m_socket, nullptr, 0, 0) < 0)
#endif
        {
            return false;
        }
        return true;
    }

    void notifyForQuit()
    {
        m_run = false;
    }

    bool sendData(const char* data, uint32_t size)
    {
        if (m_socket == INVALID_SOCKET)
            return false;
        auto ret = send(m_socket, data, size, 0);
        if (ret > 0) {
            return true;
        }
        else if (ret == 0) {
            printf("client disconnected\n");
            return false;
        }
        else {
            DEBUGSOCK("send");
            return false;
        }
    }

    const char* addr() const
    {
        return inet_ntoa(m_serverAddr.sin_addr);
    }

    uint16_t port() const
    {
        return ntohs(m_serverAddr.sin_port);
    }

private:
    bool reset()
    {
#ifdef _WIN32
        closesocket(m_socket);
#else
        close(m_socket);
#endif
        //1. Create Socket
        m_socket = socket(AF_INET, SOCK_STREAM, 0);
        if (m_socket == INVALID_SOCKET)
        {
            DEBUGSOCK("socket");
            return false;
        }

        int reuse = 1;
        NETOPTSET(m_socket, SOL_SOCKET, SO_REUSEADDR, reuse);

#ifdef _WIN32
        int wait_time = 3000;
#else
        timeval wait_time{ 3,0 };
#endif
        NETOPTSET(m_socket, SOL_SOCKET, SO_RCVTIMEO, wait_time);

        return true;
    }
    void receiveThread()
    {
        m_run = true;
        const int BUFF_SIZE = 2048;
        char buff[BUFF_SIZE] = "";
        while (m_run) {
            int size = recv(m_socket, buff, BUFF_SIZE, 0);
            if (size <= 0) {
#ifdef _WIN32
                if (WSAGetLastError() != WSAETIMEDOUT)
#else
                if (errno != EAGAIN)

#endif
                {
                    DEBUGSOCK("recv");
                    return;
                }
            }
            else {
                m_pListener(buff, size);
            }
        }
    }

    void autoConnect()
    {
        while (m_run)
        {
            if (!isConnected())
            {
                reset();
                if (connect(m_socket, reinterpret_cast<sockaddr*>(&m_serverAddr), sizeof(m_serverAddr)) < 0) {
                    //DEBUGSOCK("connect");
                }
                else {//successful connected
                    printf("tcp client successful connected(addr=%s,port = %u)\n", addr(), port());
                    if (m_recvThread.joinable())
                        m_recvThread.join();
                    m_recvThread = std::thread(&TcpClientPrivate::receiveThread, this);
                }
            }
            else {
                //printf("Now in connected\n");
            }
            std::this_thread::sleep_for(std::chrono::seconds(5));
        }
    }

private:
    SOCKET              m_socket;
    DataListener        m_pListener;
    std::thread         m_recvThread;
    std::thread         m_connThread;
    bool                m_run;
    sockaddr_in         m_serverAddr;
};

TcpClient::TcpClient(const std::string& addr, uint16_t port, DataListener pListener)
    :d(new TcpClientPrivate(pListener, addr, port))
{}

TcpClient::TcpClient(const NetAddress& na, DataListener pListener)
    : d(new TcpClientPrivate(pListener, TransAddr(na.ipValue()), na.port()))
{}

TcpClient::~TcpClient()
{
    delete d;
}

void TcpClient::notifyForQuit()
{
    d->notifyForQuit();
}

bool TcpClient::isConnected() const
{
    return d->isConnected();
}

bool TcpClient::sendData(const char* data, uint32_t size)
{
    return d->sendData(data, size);
}

const char* TcpClient::addr() const
{
    return d->addr();
}

uint16_t TcpClient::port() const
{
    return d->port();
}

}
}//namespace tool::net
